#include <math.h>
#include <crtdbg.h>
#include <string.h>
#include <new>

#include "toolbox.hpp"
#include "../../rdx/src/lua/src/lauxlib.h"
#include "../../rdx/src/lua/src/lualib.h"
#include "../freeimage/FreeImage.h"
#include "../libs/squish-1.10/squish.h"

#include "../tdp2/tdp2_threading.hpp"
#include "../tdp2/tdp2_ddsfile.hpp"
#include "../tdp2/tdp2_vector.hpp"
#include "../tdp2/tdp2_random.hpp"

using namespace TDP;
using namespace TDP::Containers;
using namespace TDP::Rendering;
using namespace std;


static void VolCopy(volatile void *dest, const void *src, size_t size)
{
	volatile char *destc = reinterpret_cast<volatile char *>(dest);
	const char *srcc = reinterpret_cast<const char *>(src);

	while(size--)
		*destc++ = *srcc++;
}

static void SRGB255ToLinear255(const float inCluster[16][4], float outCluster[16][4])
{
	for(int i=0;i<16;i++)
	{
		outCluster[i][3] = inCluster[i][3];
		for(int j=0;j<3;j++)
		{
			if(inCluster[i][j] <= 10.31475)
				outCluster[i][j] = inCluster[i][j] / 12.92f;
			outCluster[i][j] = powf( (inCluster[i][j] + 14.025f) * 0.037405f, 2.4f);
		}
	}
}

static void Linear255ToSRGB255(const float inCluster[16][4], float outCluster[16][4])
{
	for(int i=0;i<16;i++)
	{
		outCluster[i][3] = inCluster[i][3];
		for(int j=0;j<3;j++)
		{
			if(inCluster[i][j] < 0.798354)
				outCluster[i][j] = inCluster[i][j] * 12.92f;
			outCluster[i][j] = 26.7343938f * powf(inCluster[i][j], 1.0f / 2.4f) - 14.025f;
		}
	}
}

static void Linear255ToGamma2_255(const float inCluster[16][4], float outCluster[16][4])
{
	for(int i=0;i<16;i++)
	{
		outCluster[i][3] = inCluster[i][3];
		for(int j=0;j<3;j++)
		{
			if(inCluster[i][j] < 0.0f)
				outCluster[i][j] = 0.0f;
			outCluster[i][j] = sqrtf(inCluster[i][j]) * 15.968719f;
		}
	}
}

static void Gamma2_255ToLinear255(const float inCluster[16][4], float outCluster[16][4])
{
	for(int i=0;i<16;i++)
	{
		outCluster[i][3] = inCluster[i][3];
		for(int j=0;j<3;j++)
			outCluster[i][j] = inCluster[i][j]*inCluster[i][j] / 255.0f;
	}
}

//
//R = Fr*(255.0/74.0)*Y
//G = Fg*(255.0/150.0)*Y
//B = ((255.0/31.0) - (255.0/31.0)*Fr - (255.0/31.0)*Fg)*Y
static void Linear255ToFastYFrFg(const float inCluster[16][4], float outCluster[16][4])
{
	for(int i=0;i<16;i++)
	{
		float iY = inCluster[i][0]*74.0f + inCluster[i][1]*150.0f + inCluster[i][2]*31.0f;
		float iCr, iCg;

		if(iY == 0.0f)
			iCr = iCg = 0.0f;
		else
		{
			iCr = (18870.0f*inCluster[i][0] + (iY*0.5f)) / iY;
			iCg = (38250.0f*inCluster[i][1] + (iY*0.5f)) / iY;
		}
		outCluster[i][0] = floorf(iCr);
		outCluster[i][1] = floorf(iCg);
		outCluster[i][2] = 0.0f;
		outCluster[i][3] = floorf((iY + 127.5f) / 255.0f);
	}
}

static void EncodeDXT5NormalMap(const float inCluster[16][4], float outCluster[16][4])
{
	for(int i=0;i<16;i++)
	{
		float x = (inCluster[i][0] - 127.5f) / 127.5f;
		float y = (inCluster[i][1] - 127.5f) / 127.5f;
		float z = (inCluster[i][2] - 127.5f) / 127.5f;

		float l = sqrtf(x*x + y*y + z*z);
		if(l == 0.0f)
			l = 1.0f;

		outCluster[i][0] = 0.0f;
		outCluster[i][1] = (y / l) * 127.5f + 127.5f;
		outCluster[i][2] = (z / l) * 255.0f;
		outCluster[i][3] = (x / l) * 127.5f + 127.5f;
	}
}


typedef void (*filterFunc_t) (const float inCluster[16][4], float outCluster[16][4]);


// This needs to match the list in compileresources.lua
static filterFunc_t filters[] =
{
	Linear255ToSRGB255,
	SRGB255ToLinear255,
	Linear255ToGamma2_255,
	Gamma2_255ToLinear255,
	Linear255ToFastYFrFg,
	EncodeDXT5NormalMap,
};


struct HDPixel
{
	float rgba[4];
	bool isUnsigned;
};

struct HDImage : public ScriptObject
{
	unsigned int width, height;
	HDPixel elements[1];

	inline HDImage(unsigned int w, unsigned int h)
	{
		objType = SOC_HDImage;
		width = w;
		height = h;
	}
};


class ImageProcessor : public TDP::Threading::SimpleJob
{
protected:
	unsigned int _width;
	unsigned int _height;
	unsigned int _clusterSize;
	unsigned int _numUnits;
	unsigned int _x;
	unsigned int _y;
	HDImage **_imgs;
	volatile RDX::AtomicInt _currentUnit;

public:
	ImageProcessor(HDImage **imgs, unsigned int clusterSize)
	{
		_width = imgs[0]->width;
		_height = imgs[0]->height;
		_x = 0;
		_y = 0;
		_imgs = imgs;
		_clusterSize = clusterSize;
		_numUnits = (_width/clusterSize) * (_height/clusterSize);
		TDP::Threading::NonAtomicWrite(&_currentUnit, 0);
	}

	void *GetWork()
	{
		NonAtomicInt unit = TDP::Threading::AtomicIncrement(&_currentUnit) - 1;
		if(unit >= _numUnits)
			return NULL;

		char *basePtr = reinterpret_cast<char *>(NULL) + 1 + unit;
		return basePtr;
	}

	virtual void ProcessPoint(unsigned int x, unsigned int y) = 0;

	void Work(void *ptr)
	{
		unsigned int unitNo = static_cast<unsigned int>(reinterpret_cast<char *>(ptr) - reinterpret_cast<char *>(NULL) - 1);

		unsigned int x = (unitNo * _clusterSize) % _width;
		unsigned int y = (unitNo / (_width / _clusterSize)) * _clusterSize;

		ProcessPoint(x, y);
	}
};

class SelfShadowingImageProcessor : public ImageProcessor
{
	const HDImage *_baseImage;
	const HDImage *_normalMapImage;
	unsigned int _numRays;
	float _heightMultiplier;

public:
	SelfShadowingImageProcessor(HDImage **ssImgs, const HDImage *base, const HDImage *nmImage, unsigned int numRays, float heightMultiplier) :
		ImageProcessor(ssImgs, 1)
	{
		_baseImage = base;
		_normalMapImage = nmImage;
		_numRays = numRays;
		_heightMultiplier = heightMultiplier;
	}

	static void DirectionToBands(const Math::FVec3 &dir, float coefs[9])
	{
		// Shader-level decode bands
		coefs[0] = 1.0f;

		coefs[1] = dir[1];
		coefs[2] = dir[2];
		coefs[3] = dir[0];

		coefs[4] = ( dir[0]*dir[1] );
		coefs[5] = ( dir[1]*dir[2] );
		coefs[6] = ( 1.5f*( dir[2]*dir[2] ) - 0.5f );	// sqrt(3)
		coefs[7] = ( dir[0]*dir[2] );
		coefs[8] = 0.5f*( dir[0]*dir[0] - dir[1]*dir[1] );
	}

	void ProcessPoint(unsigned int x, unsigned int y)
	{
		const HDImage *base = _baseImage;
		const HDImage *nm = _normalMapImage;
		unsigned int width = base->width;
		unsigned int height = base->height;
		Randomizer rng(1000);//rng((x << 16) | y);

		unsigned int maxSamples = 100;
		const unsigned int NUM_BANDS = 9;
		const unsigned int NUM_COEFS_PER_BAND = 3;

		float factors[NUM_BANDS];
		float weights[NUM_BANDS];
		for(unsigned int i=0;i<NUM_BANDS;i++)
		{
			factors[i] = 0.0f;
			weights[i] = 0.0f;
		}

		float initialMaxHeight = base->elements[y*width+x].rgba[0];
		Math::FVec3 normal;
		if(nm)
			normal = (Math::FVec3(nm->elements[y*width+x].rgba) - Math::FVec3(127.5f)).Normalize2();
		else
			normal = Math::FVec3(0.0f, 0.0f, 1.0f);

		float rayIntensity = 1.0f;

		for(unsigned int ray=0;ray<_numRays;ray++)
		{
			Math::FVec3 dir = rng.RandomDirection();
			rayIntensity = dir.DotProduct(normal);
			if(rayIntensity <= 0.0f)
				rayIntensity = 0.0f;
			else
			{
				float heightIncrement = dir[2] / _heightMultiplier;

				float traceX = (float)x;
				float traceY = (float)y;
				float maxHeight = initialMaxHeight;
				bool hit = false;

				for(unsigned int sample=0;sample<maxSamples;sample++)
				{
					traceX += dir[0];
					traceY += dir[1];
					maxHeight += heightIncrement;

					if(maxHeight >= 255.0f)
						break;	// Cleared

					int imgX = (int)traceX;
					int imgY = (int)traceY;

					while(imgX < 0)
						imgX += width;
					while(imgY < 0)
						imgY += height;

					imgX = imgX % (int)width;
					imgY = imgY % (int)height;

					if(base->elements[imgY*width+imgX].rgba[0] >= maxHeight)
					{
						hit = true;
						break;
					}
				}

				if(hit == true)
					rayIntensity = 0.0f;
			}

			float bandWeights[NUM_BANDS];
			DirectionToBands(dir, bandWeights);

			float quadLength = bandWeights[4]*bandWeights[4]+
				bandWeights[5]*bandWeights[5]+
				bandWeights[6]*bandWeights[6]+
				bandWeights[7]*bandWeights[7]+
				bandWeights[8]*bandWeights[8];

			for(unsigned int i=0;i<NUM_BANDS;i++)
			{
				factors[i] += bandWeights[i]*rayIntensity;
				weights[i] += 1.0f;
			}
		}

		for(unsigned int i=0;i<NUM_BANDS;i++)
			factors[i] /= weights[i];

		// Lambert integration corrections
		factors[0] *= 4.0f;
		for(int i=1;i<4;i++)
			factors[i] *= 6.0f;
		for(int i=4;i<9;i++)
			factors[i] *= 16.0f;

		for(int band=0;band<NUM_BANDS;band++)
		{
			int imgIndex = band / NUM_COEFS_PER_BAND;
			int coefIndex = band % NUM_COEFS_PER_BAND;

			HDPixel *px = _imgs[imgIndex]->elements + y*width + x;

			if(coefIndex == 0)
			{
				px->isUnsigned = true;

				px->rgba[0] = px->rgba[1] = px->rgba[2] = 0.0f;
				px->rgba[3] = 255.0f;
			}
			px->rgba[coefIndex] = 127.5f + factors[band] * 127.5f;
		}
	}

	void Twiddle()
	{
	}
};

class LightmapBuildJob : public TDP::Threading::SimpleJob
{
	volatile AtomicInt _unit;
	int _numUnits;

	int _lmWidth;
	int _lmHeight;
	float _maxMagnitude;
	int _chartMode;

	const HDImage *_source;
	volatile unsigned char *_constantDest;
	volatile unsigned char *_lqDest;
	volatile unsigned char *_singleDest;
	volatile unsigned char **_planes;


public:
	LightmapBuildJob(const HDImage *source,
		volatile unsigned char *constantDest, volatile unsigned char *lqDest, volatile unsigned char *singleDest, float maxMagnitude,
		int chartMode)
	{
		_source = source;
		_lmWidth = source->width;
		_lmHeight = source->height / 10;
		_constantDest = constantDest;
		_lqDest = lqDest;
		_singleDest = singleDest;
		_numUnits = (_lmWidth/4) * (_lmHeight/4);
		RDX::Threading::NonAtomicWrite(&_unit, 0);
		_maxMagnitude = maxMagnitude;
		_chartMode = chartMode;
	}

	LightmapBuildJob(const HDImage *source,
		volatile unsigned char **planes, int numPlanes, float maxMagnitude, int chartMode)
	{
		_source = source;
		_lmWidth = source->width;
		_lmHeight = source->height / numPlanes;

		_planes = planes;
		_numUnits = (_lmWidth/4) * (_lmHeight/4);
		RDX::Threading::NonAtomicWrite(&_unit, 0);
		_maxMagnitude = maxMagnitude;
		_chartMode = chartMode;
	}

	virtual void *GetWork()
	{
		int unitNum = static_cast<int>(RDX::Threading::AtomicIncrement(&_unit)) - 1;
		if(unitNum >= _numUnits)
			return NULL;

		return reinterpret_cast<char*>(NULL) + unitNum + 1;
	}

	void WorkSuperDeluxe(int blockX, int blockY, int unitNum)
	{
		bool markedAny = false;
		bool emitDebug = false;
		int squishFlags = squish::kColourIterativeClusterFit | squish::kColourMetricUniform;

		float blocks[7][16][3];

		for(int i=0;i<7;i++)
			for(int j=0;j<16;j++)
			{
				int bandY = i * _lmHeight;
				int subBlockX = j % 4;
				int subBlockY = j / 4;

				int px = (bandY + blockY + subBlockY) * _lmWidth + blockX + subBlockX;

				for(int k=0;k<3;k++)
				{
					float v = _source->elements[px].rgba[k];
					blocks[i][j][k] = v;
				}
			}

		// Blocks:
		// 0: RGB
		// 1,2,3: Basis 1,2,3
		// 4: SG color
		// 5: SG direction
		// 6: PL magnitude
		float maxBasisMagnitudes[16];
		for(int px=0;px<16;px++)
		{
			float maxMag = 0.0f;
			for(int bblock=1;bblock<=3;bblock++)
			{
				for(int channel=0;channel<3;channel++)
				{
					float v = blocks[bblock][px][channel];
					if(v > maxMag)
						maxMag = v;
				}
			}
			if(maxMag > _maxMagnitude)
				maxBasisMagnitudes[px] = _maxMagnitude;
			else
				maxBasisMagnitudes[px] = maxMag;
		}

		unsigned char basisUncompressed[7][16*4];
		unsigned char basisCompressed[7][16];
		for(int basis=0;basis<3;basis++)
		{
			for(int px=0;px<16;px++)
			{
				unsigned char *bout = basisUncompressed[basis] + px*4;
				if(maxBasisMagnitudes[px] == 0.0f)
				{
					for(int channel=0;channel<3;channel++)
						bout[channel] = 0;
				}
				else
				{
					for(int channel=0;channel<3;channel++)
					{
						float v = blocks[basis+1][px][channel] * 255.0f / maxBasisMagnitudes[px];
						if(v < 0.0f)
							bout[channel] = 0;
						else if(v > 255.0f)
							bout[channel] = 255;
						else
							bout[channel] = static_cast<unsigned char>(v);
					}
				}


				if(basis == 0)
				{
					// First value is sqrt(scaled magnitude)
					unsigned char encodedMagnitude = static_cast<unsigned char>(sqrtf(maxBasisMagnitudes[px] / _maxMagnitude) * 255.0f);
					if(encodedMagnitude < 1)
						encodedMagnitude = 1;
					bout[3] = encodedMagnitude;
				}
				else if(basis == 1)
				{
					// Second value is corrected
					float baseMag = static_cast<float>(basisUncompressed[0][px*4+3]);
					if(baseMag == 0.0f)
						baseMag = 1.0f;
					float baseCorrection = maxBasisMagnitudes[px]*65025.0f/_maxMagnitude/baseMag;
					
					if(baseCorrection > 255.0f)
						bout[3] = 255;
					else
						bout[3] = static_cast<unsigned char>(baseCorrection);
				}
				else if(basis == 2)
				{
					// Third value is PL weight
					float v = blocks[6][px][0];
					if(v > 255.0f)
						bout[3] = 255;
					else if(v < 0.0f)
						bout[3] = 0;
					else
						bout[3] = static_cast<unsigned char>(v);
				}
			}

			squish::Compress(basisUncompressed[basis], basisCompressed[basis], squishFlags | squish::kDxt5);
			if(basis == 0)	// Need to decompress to get the actual stored intensity
				squish::Decompress(basisUncompressed[basis], basisCompressed[basis], squish::kDxt5);
		}

		// Encode SG color
		for(int px=0;px<16;px++)
		{
			unsigned char *bout = basisUncompressed[3] + px*4;
			float maxMag = 0.0f;
			for(int i=0;i<3;i++)
			{
				float v = blocks[4][px][i];
				if(v > maxMag)
					maxMag = v;
			}
			if(maxMag == 0.0f)
				bout[0] = bout[1] = bout[2] = bout[3] = 0;
			else
			{
				if(maxMag > _maxMagnitude)
					maxMag = _maxMagnitude;
				for(int i=0;i<3;i++)
				{
					float v = blocks[4][px][i] * 255.0f / maxMag;
					if(v > 255.0f)
						bout[i] = 255;
					else
						bout[i] = static_cast<unsigned char>(v);
				}
				bout[3] = maxMag * 255.0f / _maxMagnitude;
			}
		}
		squish::Compress(basisUncompressed[3], basisCompressed[3], squishFlags | squish::kDxt5);
		
		// Encode SG direction
		for(int px=0;px<16;px++)
		{
			unsigned char *bout = basisUncompressed[4] + px*4;
			float xyz[3];
			for(int i=0;i<3;i++)
				xyz[i] = blocks[5][px][i];

			// Normalize direction
			double len = sqrt(xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2]);
			if(len > 0.0)
				for(int i=0;i<3;i++)
					xyz[i] /= len;

			xyz[0] = 127.5f * xyz[0] + 127.5f;
			xyz[1] = 127.5f * xyz[1] + 127.5f;
			xyz[2] = 255.0f * xyz[2];

			unsigned char xyzBytes[3];
			for(int i=0;i<3;i++)
			{
				if(xyz[i] < 0.0f)
					xyzBytes[i] = 0;
				else if(xyz[i] > 255.0f)
					xyzBytes[i] = 255;
				else
					xyzBytes[i] = static_cast<unsigned char>(xyz[i]);
			}

			bout[0] = xyzBytes[0];
			bout[1] = xyzBytes[1];
			bout[2] = 0;
			bout[3] = xyzBytes[2];
		}
		squish::Compress(basisUncompressed[4], basisCompressed[4], squishFlags | squish::kDxt5);

		
		// Determine constant mags
		float cTargets[16][4];
		for(int px=0;px<16;px++)
		{
			float mag = 0.0f;
			for(int i=0;i<3;i++)
			{
				if(blocks[0][px][i] > mag)
					mag = blocks[0][px][i];
			}
			if(mag > _maxMagnitude)
				mag = _maxMagnitude;
			
			for(int i=0;i<3;i++)
			{
				if(mag > 0.0f)
					cTargets[px][i] = blocks[0][px][i] / mag;
			}
			cTargets[px][3] = mag / _maxMagnitude;
		}
		
		// FIXME: This is less accurate than the SH version
		for(int px=0;px<16;px++)
		{
			unsigned char *bout = basisUncompressed[5] + px*4;
			for(int i=0;i<4;i++)
			{
				double encodedBase = sqrtf(cTargets[px][i]) * 255.0;
				if(encodedBase > 255.0)
					encodedBase = 255.0;
				else if(encodedBase < 1.0)
					encodedBase = 1.0;
				unsigned char encoded = static_cast<unsigned char>(encodedBase);
				bout[i] = encoded;
			}
		}

		unsigned char copied[16*4];
		memcpy(copied, basisUncompressed[5], 16*4);

		squish::Compress(basisUncompressed[5], basisCompressed[5], squishFlags | squish::kDxt5);
		squish::Decompress(basisUncompressed[5], basisCompressed[5], squish::kDxt5);

		for(int px=0;px<16;px++)
		{
			const unsigned char *bBase = basisUncompressed[5] + px*4;
			unsigned char *bout = basisUncompressed[6] + px*4;
			for(int i=0;i<4;i++)
			{
				unsigned char oldB = bBase[i];
				if(oldB == 0)
					oldB = 1;
				float correction = cTargets[px][i] * 65025.0f / static_cast<float>(oldB);
				if(correction < 0.0f)
					bout[i] = 0;
				else if(correction > 255.0f)
					bout[i] = 255;
				else
					bout[i] = static_cast<unsigned char>(correction);
			}
		}
		squish::Compress(basisUncompressed[6], basisCompressed[6], squishFlags | squish::kDxt5);

		// Copy to results
		size_t compressedRowStrideBytes = _lmWidth * 16;
		for(int i=0;i<7;i++)
			VolCopy(this->_planes[i] + unitNum * 16, basisCompressed[i], 16);
	}

	void WorkSH(int blockX, int blockY, int unitNum)
	{
		int squishFlags = squish::kColourIterativeClusterFit | squish::kColourMetricUniform;

		float blocks[10][16][3];

		for(int i=0;i<10;i++)
			for(int j=0;j<16;j++)
			{
				int bandY = i * _lmHeight;
				int subBlockX = j % 4;
				int subBlockY = j / 4;

				int px = (bandY + blockY + subBlockY) * _lmWidth + blockX + subBlockX;

				for(int k=0;k<3;k++)
					blocks[i][j][k] = _source->elements[px].rgba[k];
			}

		unsigned char rawMagBlockBase[2][16*4];
		unsigned char compressedMagBlockBase[2][16];

		unsigned char rawMagBlockCorrected[2][16*4];
		unsigned char compressedMagBlockCorrected[2][16];

		float magnitudes[2][16];

		for(int i=0;i<16;i++)
		{
			for(int mc=0;mc<2;mc++)
				for(int j=0;j<3;j++)
					rawMagBlockBase[mc][i*4+j] = 0;
			
			// Find the magnitude
			float magnitude[2];
			magnitude[0] = magnitude[1] = 0.0f;
			for(int band=0;band<10;band++)
			{
				int mc = 0;
				if(band == 9)
					mc = 1;

				for(int channel=0;channel<3;channel++)
				{
					float v = fabsf(blocks[band][i][channel]);
					if(v > magnitude[mc])
						magnitude[mc] = v;
				}
			}
			
			for(int mc=0;mc<2;mc++)
			{
				if(magnitude[mc] > _maxMagnitude)
					magnitude[mc] = _maxMagnitude;
				magnitudes[mc][i] = magnitude[mc];

				unsigned char encodedMagnitude = static_cast<unsigned char>(sqrtf(magnitude[mc] / _maxMagnitude) * 255.0f);
				if(encodedMagnitude < 1)
					encodedMagnitude = 1;
				rawMagBlockBase[mc][i*4+3] = encodedMagnitude;
			}
		}

		for(int mc=0;mc<2;mc++)
		{
			// Compress this to get the initial magnitude alpha
			squish::Compress(rawMagBlockBase[mc], compressedMagBlockBase[mc], squishFlags | squish::kDxt5);
			squish::Decompress(rawMagBlockBase[mc], compressedMagBlockBase[mc], squish::kDxt5);

			// Correct the magnitude
			for(int i=0;i<16;i++)
			{
				for(int j=0;j<3;j++)
					rawMagBlockCorrected[mc][i*4+j] = 0;

				float baseMag = static_cast<float>(rawMagBlockBase[mc][i*4+3]);
				if(baseMag == 0.0f)
					baseMag = 1.0f;
				float baseCorrection = magnitudes[mc][i]*65025.0f/_maxMagnitude/baseMag;

				if(baseCorrection > 255.0f)
					rawMagBlockCorrected[mc][i*4+3] = 255;
				else
					rawMagBlockCorrected[mc][i*4+3] = static_cast<unsigned char>(baseCorrection);
			}
		
			// Compress and recover
			squish::Compress(rawMagBlockCorrected[mc], compressedMagBlockCorrected[mc], squishFlags | squish::kDxt5);
			squish::Decompress(rawMagBlockCorrected[mc], compressedMagBlockCorrected[mc], squish::kDxt5);

			// Determine final magnitudes
			for(int i=0;i<16;i++)
			{
				float mag = static_cast<float>(rawMagBlockCorrected[mc][i*4+3]) *
					static_cast<float>(rawMagBlockBase[mc][i*4+3]) * _maxMagnitude / 65025.0f;
				if(mag == 0)
					magnitudes[mc][i] = 1.0f;
				else
					magnitudes[mc][i] = mag;
			}
		}

		for(int band=0;band<10;band++)
		{
			unsigned char rawBlockBase[16*4];
			unsigned char compressedBlockBase[16];

			unsigned char rawBlockCorrected[16*4];
			unsigned char compressedBlockCorrected[16];

			int mc = 0;
			bool isMagBand = (band == 0);
			if(band == 9)
			{
				mc = 1;
				isMagBand = true;
			}

			for(int i=0;i<16;i++)
			{
				rawBlockBase[i*4+3] = 255;
				for(int channel=0;channel<3;channel++)
				{
					float v;
					if(band == 0)
						v = sqrtf(blocks[band][i][channel] / magnitudes[mc][i]) * 255.0f ;
					else
					{
						float baseV = blocks[band][i][channel];
						bool negative = (baseV < 0.0f);
						if(negative)
							v = -sqrtf(-baseV / magnitudes[mc][i]) * 127.5f + 127.5f;
						else
							v = sqrtf(baseV / magnitudes[mc][i]) * 127.5f + 127.5f;
					}

					if(v < 0.0f)
						rawBlockBase[i*4+channel] = 0;
					else if(v > 255.0f)
						rawBlockBase[i*4+channel] = 255;
					else
						rawBlockBase[i*4+channel] = static_cast<unsigned char>(v);
				}
			}
			
			// Compress and recover
			int cMethod = squish::kDxt1;
			if(isMagBand)
				cMethod = squish::kDxt5;

			squish::Compress(rawBlockBase, compressedBlockBase, squishFlags | cMethod);
			squish::Decompress(rawBlockBase, compressedBlockBase, cMethod);
			if(isMagBand)
			{
				// Copy in alpha from mag data
				memcpy(compressedBlockBase, compressedMagBlockBase[mc], 8);
			}

			for(int i=0;i<16;i++)
			{
				rawBlockCorrected[i*4+3] = 255;
				for(int channel=0;channel<3;channel++)
				{
					float baseV;
					float correctionV;
					baseV = static_cast<float>(rawBlockBase[i*4+channel]);
					if(band == 0)
					{
						if(baseV == 0.0f)
							baseV = 1.0f;
						correctionV = blocks[band][i][channel] * 65025.0f / magnitudes[mc][i] / baseV;
					}
					else
						correctionV = blocks[band][i][channel] * 32512.5f / magnitudes[mc][i] / (baseV - 127.5);

					if(correctionV < 0.0)
						rawBlockCorrected[i*4+channel] = 0;
					else if(correctionV > 255.0f)
						rawBlockCorrected[i*4+channel] = 255;
					else
						rawBlockCorrected[i*4+channel] = static_cast<unsigned char>(correctionV);
				}
			}
			
			squish::Compress(rawBlockCorrected, compressedBlockCorrected, squishFlags | cMethod);
			
			if(band == 0)
			{
				// Copy in alpha from mag data
				memcpy(compressedBlockCorrected, compressedMagBlockCorrected[mc], 8);
				VolCopy(_constantDest + unitNum*16, compressedBlockBase, 16);
				VolCopy(_constantDest + (_lmWidth*_lmHeight /* /4/4*16*/ ) + unitNum*16, compressedBlockCorrected, 16);
			}
			else if(band == 9)
			{
				// Copy in alpha from mag data
				memcpy(compressedBlockCorrected, compressedMagBlockCorrected[mc], 8);
				VolCopy(_singleDest + unitNum*16, compressedBlockBase, 16);
				VolCopy(_singleDest + (_lmWidth*_lmHeight /* /4/4*16*/ ) + unitNum*16, compressedBlockCorrected, 16);
			}
			else
			{
				int finalBlockX = (band-1) % 4 * _lmWidth + blockX;
				int finalBlockY = (band-1) / 4 * _lmHeight + blockY;

				// For rows of 4x4 blocks...
				size_t compressedRowStrideBytes = _lmWidth * 8;

				VolCopy(_lqDest + (finalBlockY / 4) * compressedRowStrideBytes + (finalBlockX / 4) * 8, compressedBlockBase, 8);
				VolCopy(_lqDest + ((finalBlockY + _lmHeight * 2) / 4) * compressedRowStrideBytes + (finalBlockX / 4) * 8, compressedBlockCorrected, 8);
			}
		}
	}

	
	virtual void Work(void *ptr)
	{
		int unitNum = static_cast<int>(reinterpret_cast<char*>(ptr) - reinterpret_cast<char*>(NULL)) - 1;
		int blockX = unitNum % (_lmWidth / 4) * 4;
		int blockY = unitNum / (_lmWidth / 4) * 4;

		switch(_chartMode)
		{
		case 2:
			WorkSuperDeluxe(blockX, blockY, unitNum);
			break;
		case 3:
			// SH
			WorkSH(blockX, blockY, unitNum);
			break;
		}
	}


	void Twiddle()
	{
	}
};


// hdimage, maxMagnitude
// Builds 2 HDR corrective lightmap images
static int BuildSuperDeluxeLightmap(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));
	float maxMagnitude = static_cast<float>(lua_tonumber(L, 2));

	int width = img->width;
	int height = img->height;
	int subMapWidth = width;
	int subMapHeight = height/7;

	int numPlaneBytes = subMapWidth*subMapHeight;	// 16 bytes per 4x4 cluster
	volatile unsigned char *planeBytes[7];
	for(int i=0;i<7;i++)
		planeBytes[i] = new unsigned char[numPlaneBytes];

	LightmapBuildJob buildJob(img, planeBytes, 7, maxMagnitude, 2);

	TDP::Threading::SimpleThreadDispatcher dispatcher;
	dispatcher.DigestJob(&buildJob, false, false);
	
	for(int i=0;i<7;i++)
	{
		const unsigned char *ustr = const_cast<const unsigned char *>(planeBytes[i]);

		lua_pushlstring(L, reinterpret_cast<const char *>(ustr), numPlaneBytes);
		delete[] planeBytes[i];
	}

	return 7;
}

// hdimage, maxMagnitude
// Builds 2 HDR corrective lightmap images
static int BuildSHLightmap(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));
	float maxMagnitude = static_cast<float>(lua_tonumber(L, 2));

	int width = img->width;
	int height = img->height;
	int subMapWidth = width;
	int subMapHeight = height/10;

	int numConstantBytes = (subMapWidth/4)*(subMapHeight/4)*2*16;
	int numLQBytes = (subMapWidth/4)*(subMapHeight/4)*8*16;
	int numSingleBytes = (subMapWidth/4)*(subMapHeight/4)*2*16;
	unsigned char *constantBytes = new unsigned char[numConstantBytes];
	unsigned char *lqBytes = new unsigned char[numLQBytes];
	unsigned char *singleBytes = new unsigned char[numLQBytes];

	LightmapBuildJob buildJob(img, constantBytes, lqBytes, singleBytes, maxMagnitude, 3);
	TDP::Threading::SimpleThreadDispatcher dispatcher;
	dispatcher.DigestJob(&buildJob, false, false);

	lua_pushlstring(L, reinterpret_cast<const char *>(constantBytes), numConstantBytes);
	lua_pushlstring(L, reinterpret_cast<const char *>(lqBytes), numLQBytes);
	lua_pushlstring(L, reinterpret_cast<const char *>(singleBytes), numSingleBytes);

	delete[] constantBytes;
	delete[] lqBytes;
	delete[] singleBytes;

	return 3;
}

static int ComputeShadowing(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));
	const HDImage *nmImg = reinterpret_cast<HDImage*>(lua_touserdata(L, 2));

	unsigned int width = img->width;
	unsigned int height = img->height;

	if(nmImg && width != nmImg->width && height != nmImg->height)
		return 0;

	HDImage *shadowImgs[3];
	
	for(int i=0;i<3;i++)
	{
		shadowImgs[i] = reinterpret_cast<HDImage *>(lua_newuserdata(L, sizeof(HDImage)+sizeof(HDPixel)*width*height));
		new (shadowImgs[i]) HDImage(width, height);
		memset(shadowImgs[i]->elements, 0, sizeof(HDPixel)*width*height);
	}

	SelfShadowingImageProcessor p(shadowImgs, img, nmImg, 1000, 0.1f);
	TDP::Threading::SimpleThreadDispatcher dispatch;
	dispatch.DigestJob(&p, false, false);

	return 3;
}

// original image
// returns true if the source image has non-white alpha
static int HasAlpha(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));

	unsigned int width = img->width;
	unsigned int height = img->height;
	unsigned int nPixels = width*height;

	for(int i=0;i<nPixels;i++)
	{
		if(img->elements[i].rgba[3] != 255.0f)
		{
			lua_pushboolean(L, 1);
			return 1;
		}
	}

	lua_pushboolean(L, 0);
	return 1;
}

// original image, merge image, channel index
// returns merged image
static int MergeChannel(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));
	const HDImage *mergeImg = reinterpret_cast<HDImage*>(lua_touserdata(L, 2));
	int channelIndex = lua_tointeger(L, 3);

	unsigned int width = img->width;
	unsigned int height = img->height;

	if(mergeImg->width != width || mergeImg->height != height)
		return 0;

	HDImage *mergedImage = reinterpret_cast<HDImage *>(lua_newuserdata(L, sizeof(HDImage)+sizeof(HDPixel)*width*height));
	new (mergedImage) HDImage(width, height);

	int nPixels = width*height;
	for(int i=0;i<nPixels;i++)
	{
		for(int j=0;j<4;j++)
			mergedImage->elements[i].rgba[j] = img->elements[i].rgba[j];
		mergedImage->elements[i].rgba[channelIndex] = mergeImg->elements[i].rgba[0];
	}

	return 1;
}


// original image
// returns new image based on the first with premultiplied alpha
static int PremultiplyAlpha(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));

	unsigned int width = img->width;
	unsigned int height = img->height;

	HDImage *newImage = reinterpret_cast<HDImage *>(lua_newuserdata(L, sizeof(HDImage)+sizeof(HDPixel)*width*height));
	new (newImage) HDImage(width, height);

	int nPixels = width*height;
	for(int i=0;i<nPixels;i++)
	{
		for(int j=0;j<3;j++)
			newImage->elements[i].rgba[j] = img->elements[i].rgba[j] * img->elements[i].rgba[3] / 255.0f;
		newImage->elements[i].rgba[3] = img->elements[i].rgba[3];
	}

	return 1;
}


static void QuantizeChannel(unsigned char *ldpx, unsigned int w, unsigned int h, unsigned int bits, unsigned int c)
{
	unsigned char *basePixels;
	Vector<int> diffusionBuf;
	int *diffusion, *nextDiffusion;
	int expectedValue, quantizedValue, quantError;
	unsigned int x, y;
	int roundOff, maximum;

	if(!bits)
		return;

	roundOff = (1 << (7 - bits)) * 16;
	maximum = (1 << bits) - 1;

	basePixels = ldpx + c;

	// Allocate diffusion values
	diffusionBuf.Resize(NULL, w * 2, 0);

	for(y=0;y<h;y++)
	{
		// Set diffusion buffers
		if(y & 1)
		{
			diffusion = diffusionBuf;
			nextDiffusion = diffusionBuf + w;
		}
		else
		{
			diffusion = diffusionBuf + w;
			nextDiffusion = diffusionBuf;
		}

		for(x=0;x<w;x++)
			nextDiffusion[x] = 0;

		for(x=0;x<w;x++)
		{
			expectedValue = (*basePixels) * 16;
			quantizedValue = (expectedValue + diffusion[x] + roundOff) >> (12 - bits);
			if(quantizedValue > maximum)
				quantizedValue = maximum;
			else if(quantizedValue < 0)
				quantizedValue = 0;
			quantError = expectedValue - (quantizedValue * 255 / maximum) * 16;
			quantizedValue <<= (8 - bits);

			// Diffuse error to the next pixel and the next row
			nextDiffusion[x] = 5 * quantError / 16;
			if(x)
				nextDiffusion[x-1] = 3 * quantError / 16;
			if(x < w-1)
			{
				nextDiffusion[x+1] = quantError / 16;
				diffusion[x+1] = 7 * quantError / 16;
			}

			*basePixels = (unsigned char)quantizedValue;
			basePixels += 4;
		}
	}
}

class DXTEncodeJob : public TDP::Threading::SimpleJob
{
	volatile AtomicInt _currentUnit;
	int _numUnits;
	const unsigned char *_pixels;
	unsigned char *_compressedData;
	int _width;
	int _height;
	int _squishFlags;
	int _bytesPerBlock;

public:
	DXTEncodeJob(int width, int height, int bytesPerBlock, int squishFlags, const unsigned char *pixels)
	{
		int numHBlocks = width/4;
		int numVBlocks = height/4;

		if(numHBlocks < 1) numHBlocks = 1;
		if(numVBlocks < 1) numVBlocks = 1;

		_numUnits = numVBlocks * numHBlocks;
		RDX::Threading::NonAtomicWrite(&_currentUnit, 0);
		_compressedData = NULL;
		_compressedData = new unsigned char[_numUnits * bytesPerBlock];
		_width = width;
		_height = height;
		_squishFlags = squishFlags;
		_pixels = pixels;
		_bytesPerBlock = bytesPerBlock;
	}

	~DXTEncodeJob()
	{
		if(_compressedData)
			delete[] _compressedData;
	}

	void *GetWork()
	{
		NonAtomicInt unit = TDP::Threading::AtomicIncrement(&_currentUnit) - 1;
		if(unit >= _numUnits)
			return NULL;

		char *basePtr = reinterpret_cast<char *>(NULL) + 1 + unit;
		return basePtr;
	}
	
	void Work(void *ptr)
	{
		unsigned int unitNo = static_cast<unsigned int>(reinterpret_cast<char *>(ptr) - reinterpret_cast<char *>(NULL) - 1);
		unsigned int x = (unitNo * 4) % _width;
		unsigned int y = (unitNo / (_width / 4)) * 4;

		unsigned char block[16*4];
		unsigned char compressed[16];

		for(unsigned int i=0;i<16;i++)
		{
			unsigned int vx = x;
			unsigned int vy = y;
			if(vx >= _width) vx = _width-1;
			if(vy >= _height) vy = _height-1;
			unsigned int inOffset = (vy + i/4)*_width + i%4 + vx;
			memcpy(block + i*4, _pixels + inOffset*4, 4);
		}

		squish::Compress(block, compressed, _squishFlags);
		VolCopy(_compressedData + unitNo * _bytesPerBlock, compressed, _bytesPerBlock);
	}

	void Twiddle()
	{
	}

	const unsigned char *GetCompressedData()
	{
		return static_cast<const unsigned char *>(_compressedData);
	}
};

static int ExportCompressedDDS(lua_State *L, const unsigned char *pixels, unsigned int width, unsigned int height, unsigned int codec)
{
	unsigned int blockSize = 0;
	int flags = squish::kColourIterativeClusterFit | squish::kColourMetricUniform;

	switch(codec)
	{
	case DDSFile::CodecID::DXT1_k:
		blockSize = 8;
		flags |= squish::kDxt1;
		break;
	case DDSFile::CodecID::DXT3_k:
		blockSize = 16;
		flags |= squish::kDxt3;
		break;
	case DDSFile::CodecID::DXT5_k:
		blockSize = 16;
		flags |= squish::kDxt5;
		break;
	default:
		return 0;
	};

	DXTEncodeJob job(width, height, blockSize, flags, pixels);
	TDP::Threading::SimpleThreadDispatcher dispatch;
	dispatch.DigestJob(&job, false, false);

	lua_pushlstring(L, reinterpret_cast<const char *>(job.GetCompressedData()), (width/4)*(height/4)*blockSize);
	return 1;
}

static int ExportUncompressedDDS(lua_State *L, const unsigned char *pixels, unsigned int width, unsigned int height, const DDSFile::Format *fmt)
{
	Vector<unsigned char> dithered;

	// Result = ((original >> right) << offset)
	unsigned int cBitOffsets[4];
	unsigned int cBitCounts[4];
	unsigned int cDroppedBits[4];
	unsigned int cMask[4];


	unsigned int x, y, i, j, numPixels, b, t, bpp;
	const unsigned char *inPixels;
	unsigned char *outBytes;

	Vector<unsigned char> outBuf;

	dithered.Resize(NULL, width * height * 4);
	memcpy(dithered, pixels, dithered.Count());

	// Analyze the bit mask to determine some conversion factors
	cMask[0] = fmt->rMask;
	cMask[1] = fmt->gMask;
	cMask[2] = fmt->bMask;
	cMask[3] = fmt->aMask;

	for(i=0;i<4;i++)
	{
		for(b=0;b<32;b++)
			if(cMask[i] & (1 << b))
				break;
		cBitOffsets[i] = b;
		for(;b<32;b++)
			if(!(cMask[i] & (1 << b)))
				break;
		cBitCounts[i] = b - cBitOffsets[i];

		cDroppedBits[i] = (8 - cBitCounts[i]);

		if(cBitCounts[i])
			QuantizeChannel(dithered, width, height, cBitCounts[i], i);
	}

	numPixels = width * height;

	// Allocate an output buffer
	bpp = fmt->numBytes;
	outBuf.Resize(NULL, numPixels * bpp);

	inPixels = dithered;
	outBytes = outBuf;
	for(i=0;i<numPixels;i++)
	{
		// Merge pixels into the output number
		b = 0;
		for(j=0;j<4;j++)
		{
			// Merge into the result
			t = *inPixels++;
			t = (t >> cDroppedBits[j]) << cBitOffsets[j];
			b |= t;
		}

		// Store the number out
		for(j=0;j<bpp;j++)
		{
			*outBytes++ = (unsigned char)b;
			b >>= 8;
		}
	}

	// Write the buffer
	lua_pushlstring(L, reinterpret_cast<const char *>(outBuf.DataPtr()), numPixels * bpp);
	return 1;
}

unsigned int DetectFormat(const char *formatName, const DDSFile::Format **format)
{
	*format = NULL;

	if(!strcmp(formatName, "DXT1"))
		return DDSFile::CodecID::DXT1_k;
	else if(!strcmp(formatName, "DXT3"))
		return DDSFile::CodecID::DXT3_k;
	else if(!strcmp(formatName, "DXT5"))
		return DDSFile::CodecID::DXT5_k;
	else
	{
		for(const DDSFile::Format **fmtScan=DDSFile::Format::FormatList_k;*fmtScan;fmtScan++)
		{
			const DDSFile::Format *fmt = *fmtScan;
			if(!strcmp(fmt->name, formatName))
			{
				*format = fmt;
				break;
			}
		}
	}

	return 0;
}

// Returns the byte size of a DDS header
static int DDSHeaderSize(lua_State *L)
{
	lua_pushinteger(L, 128);
	return 1;
}

// width, height, format
static int DDSLayerSize(lua_State *L)
{
	const DDSFile::Format *format = NULL;
	unsigned int codec = DetectFormat(lua_tostring(L, 3), &format);
	if(!codec && !format)
		return 0;

	switch(codec)
	{
	case DDSFile::CodecID::DXT1_k:
	case DDSFile::CodecID::DXT3_k:
	case DDSFile::CodecID::DXT5_k:
		{
			int hBlocks = lua_tointeger(L, 1) / 4;
			int vBlocks = lua_tointeger(L, 2) / 4;

			if(hBlocks == 0) hBlocks = 1;
			if(vBlocks == 0) vBlocks = 1;

			if(codec == DDSFile::CodecID::DXT1_k)
				lua_pushinteger(L, hBlocks*vBlocks*8);
			else
				lua_pushinteger(L, hBlocks*vBlocks*16);
			return 1;
		}
		break;
	};
	
	lua_pushinteger(L, lua_tointeger(L, 1) * lua_tointeger(L, 2) * format->numBytes);
	return 1;
}

// Takes DDS header string
// Returns BuildDDSHeader parameters: format, width, height, mipMaps, isCubeMap, depth
static int ParseDDSHeader(lua_State *L)
{
	DDSFile::Header hdr;
	DDSFile::Format fmt;
	char codecFormatChars[5];
	const char *formatName = NULL;
	hdr.ParseHeader(reinterpret_cast<const UInt8 *>(lua_tostring(L, 1)), &fmt);

	unsigned int codec = hdr.Codec();

	if(codec)
	{
		for(int i=0;i<4;i++)
			codecFormatChars[i] = '\0' + ((codec >> (i*8)) & 0xff);
		codecFormatChars[4] = '\0';
		formatName = codecFormatChars;
	}
	else
	{
		for(int i=0;DDSFile::Format::FormatList_k[i];i++)
		{
			const DDSFile::Format *storedFmt = DDSFile::Format::FormatList_k[i];

			if(storedFmt->rMask == fmt.rMask &&
				storedFmt->gMask == fmt.gMask &&
				storedFmt->bMask == fmt.bMask &&
				storedFmt->aMask == fmt.aMask &&
				storedFmt->numBytes == fmt.numBytes &&
				storedFmt->hasAlpha == fmt.hasAlpha)
			{
				formatName = storedFmt->name;
				break;
			}
		}
	}

	unsigned int layers = 1;
	if(hdr.SurfaceDescFlags() & DDSFile::SurfaceDescBits::Depth_k)
		layers = hdr.Depth();

	unsigned int mipMaps = 1;
	bool isCube = false;
	
	if(hdr.CapsFlags() & DDSFile::CapsBits::MipMap_k)
		mipMaps = hdr.MipMapLevels();
	if(hdr.Caps2Flags() & DDSFile::Caps2Bits::CubeMap_k)
		isCube = true;

	if(formatName)
		lua_pushstring(L, formatName);
	else
		lua_pushnil(L);
	lua_pushinteger(L, hdr.Width());
	lua_pushinteger(L, hdr.Height());
	lua_pushinteger(L, mipMaps);
	lua_pushboolean(L, isCube ? 1 : 0);
	lua_pushinteger(L, layers);

	return 6;
}

// format, width, height, mipMaps, isCubeMap
static int BuildDDSHeader(lua_State *L)
{
	DDSFile::Header hdr;
	lua_checkstack(L, 10);

	const char *formatName = lua_tostring(L, 1);
	if(!formatName)
		return 0;

	hdr.Reset();


	const DDSFile::Format *format = NULL;

	unsigned int codec = DetectFormat(formatName, &format);
	if(!codec && !format)
		return 0;

	if(lua_type(L, 5) == LUA_TBOOLEAN && lua_toboolean(L, 5) != 0)
	{
		hdr.SetCapsFlag(DDSFile::CapsBits::Complex_k);
		hdr.SetCaps2Flag(DDSFile::Caps2Bits::CubeMap_k);
		hdr.SetCaps2Flag(DDSFile::Caps2Bits::CubeMapPX_k);
		hdr.SetCaps2Flag(DDSFile::Caps2Bits::CubeMapNX_k);
		hdr.SetCaps2Flag(DDSFile::Caps2Bits::CubeMapPY_k);
		hdr.SetCaps2Flag(DDSFile::Caps2Bits::CubeMapNY_k);
		hdr.SetCaps2Flag(DDSFile::Caps2Bits::CubeMapPZ_k);
		hdr.SetCaps2Flag(DDSFile::Caps2Bits::CubeMapNZ_k);
	}

	if(codec)
		hdr.SetCodec(codec);
	hdr.SetDimensions(lua_tointeger(L, 2), lua_tointeger(L, 3), 1, lua_tointeger(L, 4));

	const unsigned char *headerBytes = hdr.ConstructHeader(format);
	lua_pushlstring(L, reinterpret_cast<const char *>(headerBytes), 128);

	return 1;
}

// imageData, format
static int EncodeDDSImage(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));

	const DDSFile::Format *format = NULL;
	unsigned int codec = DetectFormat(lua_tostring(L, 2), &format);
	if(!codec && !format)
		return 0;

	Vector<unsigned char> px;

	px.Resize(NULL, img->width * img->height * 4);
	unsigned int n = px.Count();
	for(unsigned int i=0;i<n;i++)
	{
		float fValue = floorf(img->elements[i/4].rgba[i%4] + 0.50001);		// Account for rounding errors and round up anyway
		if(fValue < 0.0f)
			px[i] = 0;
		else if(fValue > 255.0f)
			px[i] = 255;
		else
			px[i] = (unsigned char)fValue;
	}

	if(codec)
		return ExportCompressedDDS(L, px, img->width, img->height, codec);
	if(format)
		return ExportUncompressedDDS(L, px, img->width, img->height, format);
	return 0;
}

// width, height, initial R, initial G, initial B
static int CreateImage(lua_State *L)
{
	unsigned int width = lua_tointeger(L, 1);
	unsigned int height = lua_tointeger(L, 2);

	HDImage *img = reinterpret_cast<HDImage *>(lua_newuserdata(L, sizeof(HDImage)+sizeof(HDPixel)*width*height));
	new (img) HDImage(width, height);
	memset(img->elements, 0, sizeof(HDPixel)*width*height);

	return 1;
}

static int GetPixel(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));

	unsigned int x = lua_tointeger(L, 2) - 1;
	unsigned int y = lua_tointeger(L, 3) - 1;

	if(x >= img->width || y >= img->height)
		return 0;

	const HDPixel *px = img->elements + y*img->width + x;
	lua_pushnumber(L, px->rgba[0]);
	lua_pushnumber(L, px->rgba[1]);
	lua_pushnumber(L, px->rgba[2]);
	lua_pushnumber(L, px->rgba[3]);
	lua_pushboolean(L, px->isUnsigned ? 1 : 0);
	return 5;
}


static int SetPixel(lua_State *L)
{
	HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));

	unsigned int x = lua_tointeger(L, 2) - 1;
	unsigned int y = lua_tointeger(L, 3) - 1;

	if(x >= img->width || y >= img->height)
		return 0;

	HDPixel *px = img->elements + y*img->width + x;
	px->rgba[0] = lua_tonumber(L, 4);
	px->rgba[1] = lua_tonumber(L, 5);
	px->rgba[2] = lua_tonumber(L, 6);
	px->rgba[3] = lua_tonumber(L, 7);
	px->isUnsigned = (lua_toboolean(L, 8) != 0);
	return 0;
}

static bool LoadHDRFI(lua_State *L, const char *filename)
{
	Vector<HDPixel> pixels;

	unsigned int width, height;
	unsigned int numPixels, numBytes;
	Vector<unsigned char> signBits;
	HDPixel *outPx;
	unsigned int pixelIdx;

	FILE *f;

	f = fopen(filename, "rb");
	if(!f)
		return false;
	
	try
	{
		fread(&width, 4, 1, f);
		fread(&height, 4, 1, f);

		numPixels = width * height;
		numBytes = numPixels * 12;

		// Check overflow
		if(height == 0 || width == 0 || numBytes / height / 12 != width)
			return false;

		signBits.Resize(NULL, (height * width + 7) / 8, 0);
		fread(signBits, 1, signBits.Count(), f);

		pixels.Resize(NULL, numPixels);
		outPx = pixels;

		for(unsigned int pxid=0;pxid<numPixels;pxid++)
		{
			float values[3];

			// Load a float triplet
			fread(values, 4, 3, f);
			outPx->rgba[0] = values[0];
			outPx->rgba[1] = values[1];
			outPx->rgba[2] = values[2];
			outPx->rgba[3] = 255.0f;
			outPx->isUnsigned = ((signBits[pxid>>3] & (1<<(pxid&7))) == 0);
			outPx++;
		}

		HDImage *imgp = reinterpret_cast<HDImage*>(lua_newuserdata(L, sizeof(HDPixel)*numPixels + sizeof(HDImage)));
		new (imgp) HDImage(width, height);
		memcpy(imgp->elements, pixels, sizeof(HDPixel)*numPixels);

		lua_pushinteger(L, width);
		lua_pushinteger(L, height);
		return true;
	}
	catch(...)
	{
		fclose(f);
		throw;
	}
	fclose(f);

	return false;
}


static bool LoadDTEX(lua_State *L, const char *filename)
{
	lua_State *dtexL = lua_open();
	try
	{
		luaL_openlibs(dtexL);  /* open libraries */

		if(luaL_loadfile(dtexL, filename))
			return false;

		lua_call(dtexL, 0, 0);

		lua_getglobal(dtexL, "width");
		unsigned int width = lua_tointeger(dtexL, -1);
		lua_pop(dtexL, 1);

		lua_getglobal(dtexL, "height");
		unsigned int height = lua_tointeger(dtexL, -1);
		lua_pop(dtexL, 1);

		Vector<HDPixel> pixels;
		pixels.Resize(NULL, width * height);
		
		lua_getglobal(dtexL, "pixel");
		for(unsigned int y=0;y<height;y++)
		{
			for(unsigned int x=0;x<width;x++)
			{
				HDPixel px;
				lua_pushvalue(dtexL, -1);
				lua_pushinteger(dtexL, x+1);
				lua_pushinteger(dtexL, y+1);
				lua_call(dtexL, 2, 5);

				if(lua_isnoneornil(dtexL, -1))
					px.isUnsigned = false;
				else
					px.isUnsigned = (lua_toboolean(dtexL, -1) != 0);

				if(lua_isnoneornil(dtexL, -2))
					px.rgba[3] = 255.0f;
				else
					px.rgba[3] = lua_tonumber(dtexL, -2) * 255.0f;

				px.rgba[2] = lua_tonumber(dtexL, -3) * 255.0f;
				px.rgba[1] = lua_tonumber(dtexL, -4) * 255.0f;
				px.rgba[0] = lua_tonumber(dtexL, -5) * 255.0f;

				lua_pop(dtexL, 5);

				pixels[y*width+x] = px;
			}
		}

		lua_pop(dtexL, 1);
		lua_close(dtexL);
		dtexL = NULL;

		HDImage *img = reinterpret_cast<HDImage*>(lua_newuserdata(L, sizeof(HDImage) + sizeof(HDPixel) * width * height));
		new (img) HDImage(width, height);
		memcpy(img->elements, pixels, sizeof(HDPixel) * width * height);

		lua_pushinteger(L, width);
		lua_pushinteger(L, height);
	}
	catch(...)
	{
		if(dtexL)
			lua_close(dtexL);
		throw;
	}

	return false;
}

// hdimage, filter index
static int FilterImage(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));

	filterFunc_t func = filters[lua_tointeger(L, 2)];

	int numPixels = img->width * img->height;

	HDImage *imgp = reinterpret_cast<HDImage*>(lua_newuserdata(L, sizeof(HDImage) + img->width*img->height * sizeof(HDPixel)));
	new (imgp) HDImage(img->width, img->height);

	for(int x=0;x<img->width;x+=4)
	{
		for(int y=0;y<img->height;y+=4)
		{
			int unit = 0;
			float inCluster[16][4];
			float outCluster[16][4];
			for(int subY=0;subY<4;subY++)
			{
				for(int subX=0;subX<4;subX++)
				{
					int px = x + subX;
					int py = y + subY;

					if(px >= img->width) px = img->width-1;
					if(py >= img->height) py = img->height-1;

					int pindex = py*img->width + px;

					for(int i=0;i<4;i++)
						inCluster[unit][i] = img->elements[pindex].rgba[i];
					unit++;
				}
			}

			func(inCluster, outCluster);

			unit = 0;
			for(int subY=0;subY<4;subY++)
			{
				for(int subX=0;subX<4;subX++)
				{
					int px = x + subX;
					int py = y + subY;

					if(px >= img->width) px = img->width-1;
					if(py >= img->height) py = img->height-1;

					int pindex = py*img->width + px;

					for(int i=0;i<4;i++)
						imgp->elements[pindex].rgba[i] = outCluster[unit][i];
					unit++;
				}
			}
		}
	}

	return 1;
}

static int RescaleToHalf(lua_State *L)
{
	const HDImage *img = reinterpret_cast<HDImage*>(lua_touserdata(L, 1));

	if(img->width % 2 || img->height % 2)
		return 0;

	HDImage *imgp = reinterpret_cast<HDImage*>(lua_newuserdata(L, sizeof(HDImage) + img->width*img->height / 4 * sizeof(HDPixel)));
	new (imgp) HDImage(img->width/2, img->height/2);

	for(int x=0;x<img->width;x+=2)
	{
		for(int y=0;y<img->height;y+=2)
		{
			float combined[4];
			for(int i=0;i<4;i++)
				combined[i] = 0.0f;

			for(int subY=0;subY<2;subY++)
			{
				for(int subX=0;subX<2;subX++)
				{
					int px = x + subX;
					int py = y + subY;

					int pindex = py*img->width + px;

					for(int i=0;i<4;i++)
						combined[i] += img->elements[pindex].rgba[i];
				}
			}

			for(int i=0;i<4;i++)
				imgp->elements[(y/2*imgp->width)+x/2].rgba[i] = combined[i] * 0.25f;
		}
	}



	return 1;
}

static bool LoadFI(lua_State *L, const char *filename, int format, int flags)
{
	FreeImageIO io;
	FIBITMAP *bmp, *nbmp;

	unsigned int total, x, y;
	unsigned int baseWidth, baseHeight;
	unsigned int rescaleWidth, rescaleHeight;

	bmp = FreeImage_Load((FREE_IMAGE_FORMAT)format, filename, flags);
	if(!bmp)
	{
		return false;
	}

	// Convert to 32-bits
	nbmp = FreeImage_ConvertTo32Bits(bmp);
	FreeImage_Unload(bmp);
	bmp = nbmp;
	if(!bmp)
		return false;

	// Get dimensions
	baseWidth = FreeImage_GetWidth(bmp);
	baseHeight = FreeImage_GetHeight(bmp);

	for(rescaleWidth=1;rescaleWidth<baseWidth;rescaleWidth <<= 1) {}
	for(rescaleHeight=1;rescaleHeight<baseHeight;rescaleHeight <<= 1) {}

	if(rescaleWidth > 16384 || rescaleHeight > 16384)
	{
		FreeImage_Unload(bmp);
		return false;
	}

	// Rescale as needed
	if(rescaleWidth != baseWidth || rescaleHeight != baseHeight)
	{
		nbmp = FreeImage_Rescale(bmp, rescaleWidth, rescaleHeight, FILTER_BOX);
		FreeImage_Unload(bmp);

		if(!nbmp)
			return false;
		bmp = nbmp;
	}

	// Make sure this isn't being overflowed
	total = rescaleWidth * rescaleHeight;
	if(total / rescaleWidth != rescaleHeight)
		return false;
		total *= 4;
	if(total / 4 != rescaleWidth * rescaleHeight)
		return false;

	// Convert to bytes
	HDImage *imgp;
	try
	{
		imgp = reinterpret_cast<HDImage*>(lua_newuserdata(L, sizeof(HDImage) + total * sizeof(HDPixel)));
	}
	catch(...)
	{
		FreeImage_Unload(bmp);
		throw;
	};

	new (imgp) HDImage(rescaleWidth, rescaleHeight);

	// Convert to an RGBA buffer
	HDPixel *pxp = imgp->elements;
	for(y=0;y<rescaleHeight;y++)
	{
		const unsigned char *sbp = FreeImage_GetScanLine(bmp, rescaleHeight - y - 1);
		for(x=0;x<rescaleWidth;x++)
		{
			pxp->rgba[0] = sbp[2];
			pxp->rgba[1] = sbp[1];
			pxp->rgba[2] = sbp[0];
			pxp->rgba[3] = sbp[3];
			pxp->isUnsigned = true;

			pxp++;
			sbp += 4;
		}
	}

	FreeImage_Unload(bmp);

	lua_pushinteger(L, rescaleWidth);
	lua_pushinteger(L, rescaleHeight);

	return true;
}

static int GetBuildDate(lua_State *L)
{
	lua_pushstring(L, __DATE__ "/" __TIME__);
	return 1;
}

static int LoadImage(lua_State *L)
{
	const char *path = lua_tostring(L, 1);
	Vector<char> extended;
	extended.Resize(NULL, strlen(path) + 10);

	strcpy(extended, path); strcat(extended, ".dtex");
	if(LoadDTEX(L, extended))
		return 3;

	strcpy(extended, path); strcat(extended, ".tga");
	if(LoadFI(L, extended, FIF_TARGA, TARGA_DEFAULT))
		return 3;
	
	strcpy(extended, path); strcat(extended, ".jpg");
	if(LoadFI(L, extended, FIF_JPEG, JPEG_ACCURATE))
		return 3;
	
	strcpy(extended, path); strcat(extended, ".png");
	if(LoadFI(L, extended, FIF_PNG, PNG_DEFAULT))
		return 3;

	strcpy(extended, path); strcat(extended, ".dds");
	if(LoadFI(L, extended, FIF_DDS, DDS_DEFAULT))
		return 3;

	strcpy(extended, path); strcat(extended, ".fi");
	if(LoadHDRFI(L, extended))
		return 3;

	return 0;
}




void RegisterImageTools(lua_State *L)
{
	lua_pushcfunction(L, BuildDDSHeader);
	lua_setglobal(L, "ImgTools_BuildDDSHeader");
	
	lua_pushcfunction(L, ParseDDSHeader);
	lua_setglobal(L, "ImgTools_ParseDDSHeader");
	
	lua_pushcfunction(L, DDSLayerSize);
	lua_setglobal(L, "ImgTools_DDSLayerSize");
	
	lua_pushcfunction(L, DDSHeaderSize);
	lua_setglobal(L, "ImgTools_DDSHeaderSize");
	
	lua_pushcfunction(L, EncodeDDSImage);
	lua_setglobal(L, "ImgTools_EncodeDDSImage");

	lua_pushcfunction(L, GetPixel);
	lua_setglobal(L, "ImgTools_GetPixel");

	lua_pushcfunction(L, SetPixel);
	lua_setglobal(L, "ImgTools_SetPixel");

	lua_pushcfunction(L, LoadImage);
	lua_setglobal(L, "ImgTools_LoadImage");

	lua_pushcfunction(L, CreateImage);
	lua_setglobal(L, "ImgTools_CreateImage");

	lua_pushcfunction(L, BuildSuperDeluxeLightmap);
	lua_setglobal(L, "ImgTools_BuildSuperDeluxeLightmap");

	lua_pushcfunction(L, BuildSHLightmap);
	lua_setglobal(L, "ImgTools_BuildSHLightmap");

	lua_pushcfunction(L, FilterImage);
	lua_setglobal(L, "ImgTools_FilterImage");

	lua_pushcfunction(L, RescaleToHalf);
	lua_setglobal(L, "ImgTools_RescaleToHalf");

	lua_pushcfunction(L, PremultiplyAlpha);
	lua_setglobal(L, "ImgTools_PremultiplyAlpha");

	lua_pushcfunction(L, HasAlpha);
	lua_setglobal(L, "ImgTools_HasAlpha");

	lua_pushcfunction(L, MergeChannel);
	lua_setglobal(L, "ImgTools_MergeChannel");

	lua_pushcfunction(L, ComputeShadowing);
	lua_setglobal(L, "ImgTools_ComputeShadowing");

	lua_pushcfunction(L, GetBuildDate);
	lua_setglobal(L, "ImgTools_GetBuildDate");
}
